#include "Renderer.h"
#include <iostream>
#include "Object.h"

#define PRINTVEC2(v) std::cout<< #v##" = " << v.x << "," << v.y << std::endl
#define PRINTVEC3(v) std::cout<< #v##" = " << v.x << "," << v.y << "," << v.z << std::endl
#define PRINTVEC4(v) std::cout<< #v##" = " << v.x << "," << v.y << "," << v.z << "," << v.w << std::endl


Renderer::Renderer()
{
}


Renderer::~Renderer()
{
}

int Renderer::addObject(Object* obj)
{
    int objID = _objs.size();
    _objs.push_back(obj);
    return objID;
}

void Renderer::setLight(const vec3 & pos, const vec4 & color)
{
    _lightPos = pos;
    _lightColor = color;
}

void Renderer::clearAll()
{
    _objs.clear();
}

void Renderer::render(uint w, uint h, uint bpp, void * raw_data)
{
    static scalar ang = 0;
    mat4 rotMat;
    rotMat = glm::rotate(rotMat, ang, vec3(0, 1, 0));
    ang += 0.01;
    _lightPos = rotMat * vec4(0, 0, 10, 1);

    mat4 VPW = _window * _proj * _view;
    mat4 invVPW = glm::inverse(VPW);
    unsigned char* data = (unsigned char*)raw_data;

    vec3 eye;
    mat4 invView = glm::inverse(_view);
    eye = invView[3];

    auto setPixel = [w, h, bpp, &data](int i, int j, const vec3& c)
    {
        uint pos = (w * j + i) * bpp;
        data[pos + 0] = unsigned char(c.r * 255);
        data[pos + 1] = unsigned char(c.g * 255);
        data[pos + 2] = unsigned char(c.b * 255);
        data[pos + 3] = 255;
    };

    for (int j = 0; j < h; ++j)
    {
        for (int i = 0; i < w; ++i)
        {
            vec4 z0(i, j, 0, 1);
            vec4 z1(i, j, 1, 1);
            z0 = invVPW * z0;
            z1 = invVPW * z1;
            z0 /= z0.w;
            z1 /= z1.w;
            

            vec3 hit;
            vec3 normal;
            scalar nearest = FLT_MAX;
            Object* nearestObj = NULL;
            for (const auto& obj : _objs) 
            {
                vec3 tmp_hit;
                vec3 tmp_normal;
                if (obj->rayIntersect(z0, z1, tmp_hit, tmp_normal)) 
                {
                    scalar distance = length(eye-tmp_hit);
                    if (distance < nearest) 
                    {
                        nearestObj = obj.get();
                        nearest = distance;
                        hit = tmp_hit;
                        normal = tmp_normal;
                    }
                }
            }

            bool isInShadow = false;

            if (nearestObj) 
            {
                for (const auto& obj : _objs)
                {
                    vec3 tmp_hit;
                    vec3 tmp_normal;
                    if (obj != nearestObj && obj->rayIntersect(hit, _lightPos, tmp_hit, tmp_normal)) 
                    {
                        isInShadow = true;
                        break;
                    }
                }
            }

            scalar intensity = (glm::max)(fZero, dot(norm(_lightPos-hit), normal));

            if (!isInShadow) 
            {
                if (nearestObj) 
                    setPixel(i, j, _lightColor * nearestObj->color() * intensity);
                else 
                    setPixel(i, j, _lightColor);
            }
            else 
            {
                setPixel(i, j, _lightColor * nearestObj->color() * 0.2);
            }
        }
    }
}
